package roborally.util;

import java.util.*;

import be.kuleuven.cs.som.annotate.*;
import roborally.Pieces.Robot;
/**
 * This is a class to execute a Program using the whole String of the program, 
 * the current position in the Program and a Robot that has to execute the 
 * commands.

 * @version 3.0
 * @author 	Bart Opsomer 2de Bachelor Ingenieurswetenschappen Electrotechniek-Computerwetenschappen 
 * 		   	Peter Opsomer 1ste Master Wiskundige IngenieursTechnieken
 */
@SuppressWarnings("unused")
public class CommandParser {
	// The complete program as String
	private String program;
	// The current location in the program
	private int positionInProgram;
	// The robot that runs the program
	private Robot robot;
	private List<Construct> constructCurrentlyInUse;
	/**
	 * create a new CommandParser with the given program and robot
	 * @param program the program to be executed
	 * @param robot the robot that runs the program
	 * @post	| new.getProgram() = program;
	 * @post 	| new.getRobot = robot;
	 * @post 	| new.getPositionInProgram = 0;
	 */
	public CommandParser(String program,Robot robot) {
		this.program = program;
		this.robot = robot;
		this.positionInProgram = 0;
		this.constructCurrentlyInUse = new ArrayList<Construct>();
	}
	/**
	 * execute n steps of the programs
	 * @param n the number of steps to be executed
	 */
	public void executeN(int n) {
		if(n <= 0) {
			return;
		}
		while(n != 0) {
			execute1Step();
			n--;
		}
	}
	/**
	 * @return the program of this Parser
	 */
	@Basic
	public String getProgram() {
		return this.program;
	}
	/**
	 * execute 1 step of the program, if the end is not reached
	 */
	public void execute1Step() {
		if(positionInProgram == program.length() - 1) {
			return;
		}
		boolean oneBasicCommandExecuted = false;
		while(!oneBasicCommandExecuted) {
			
			for(Construct construct : constructCurrentlyInUse) {
				if(construct.getIfAt() == this.positionInProgram) {
					this.positionInProgram = construct.getJumpTo();
					constructCurrentlyInUse.remove(construct);

					execute1Step();
					return;
				}
			}
			for(int tmpPosition = positionInProgram+1; program.charAt(tmpPosition) == ')' || program.charAt(tmpPosition) == ' '; tmpPosition ++) {
				if(program.charAt(tmpPosition) == ')') {
//					int indexOfOpeningBracket = findCorrespondingOpeningBracket(tmpPosition);
//					if(getNextWordAt(indexOfOpeningBracket).equals("while")) {
//						positionInProgram = indexOfOpeningBracket;
//						break;
//					}
					for(Construct construct : constructCurrentlyInUse) {
						if(construct.getIfAt() == tmpPosition) {
							this.positionInProgram = construct.getJumpTo();
							constructCurrentlyInUse.remove(construct);
							execute1Step();
							return;
						}
					}
				}
				if(tmpPosition == program.length()-1) {
					return;
				}
			}
			// the next word in the program
			String command = getNextEntry(true);
			//check if command is a CommandWordsBasicCommand, if yes execute it
			CommandWordsBasicCommand basicCommand = null;
			for(CommandWordsBasicCommand comm : CommandWordsBasicCommand.values()) {
				if(comm.getText().equals(command)) {
					basicCommand = comm;
				}
			}
			if(basicCommand != null) {
				executeBasicCommand(basicCommand);
				oneBasicCommandExecuted = true;
			}
			//check if command is a ComandWordsConstructs, if yes execute it
			ComandWordsConstructs construct = null;
			for(ComandWordsConstructs cons : ComandWordsConstructs.values()) {
				if(cons.getText().equals(command)) {
					construct = cons;
				}
			}
			if(construct != null) {
				Construct con = initializeConstruct(construct);
				if(con != null) {
					this.constructCurrentlyInUse.add(con);
				}
				executeConstruct(construct);
				oneBasicCommandExecuted = true;
			}
		}
	}
	private Construct initializeConstruct(ComandWordsConstructs construct) {
		int positionOfOpeningBracket = findClosestOpeningBracketLeftfrom(positionInProgram);
		int positionOFclosingBracket = findCorrespondingClosingBracket(positionOfOpeningBracket);
		Construct constructObject = null;
		if(construct.equals(ComandWordsConstructs.SEQ)) {
			//If Construct == seq no jump is necessary, so jump to the current position
			constructObject = new Construct(positionOFclosingBracket, positionOFclosingBracket, construct);
		}
		if(construct.equals(ComandWordsConstructs.WHILE)) {
			//If Construct == while jump is necessary, so jump to the position of openingBracket
			constructObject = new Construct(positionOFclosingBracket, positionOfOpeningBracket, construct);
		}
		if(construct.equals(ComandWordsConstructs.IF)) {
			//If Construct == if jump is necessary, only when the if-block is done executing 
			// so jump to after the elseBlock = positionOFclosingBracket
			int positionOfEndIfBlock = findPositionOfEndIfBlock(positionInProgram);
			constructObject = new Construct(positionOfEndIfBlock, positionOFclosingBracket, construct);
		}
		
		return constructObject;
	}
	private int findPositionOfEndIfBlock(int currentPosition) {
		int positionOfStartCondition = findClosestOpeningBracketRightfrom(currentPosition);
		int endOfCondition = findCorrespondingClosingBracket(positionOfStartCondition);
		int startOfIfBlock = findClosestOpeningBracketRightfrom(endOfCondition);
		int endOfIfBlock = findCorrespondingClosingBracket(startOfIfBlock);
		return endOfIfBlock;
	}
	private void executeConstruct(ComandWordsConstructs construct) {
		if(construct.equals(ComandWordsConstructs.SEQ)) {
			// Is applied recursively to get to next basic command for nested constructs.
			execute1Step();
		}
		else {
			// the command is while or if
			int openingBracketJustBeforeConstruct = findClosestOpeningBracketLeftfrom(positionInProgram);
			putPositionInProgramToNextOpeningBracket();
			if(executeCondition()) {
				execute1Step();
			}
			else {
				
				if(construct.equals(ComandWordsConstructs.IF)) {
					int endOfCondition = positionInProgram;
					int startOfIfBlock = findClosestOpeningBracketRightfrom(endOfCondition);
					int endOfIfBlock = findCorrespondingClosingBracket(startOfIfBlock);
					this.positionInProgram = findClosestOpeningBracketRightfrom(endOfIfBlock);
					
					for(Construct con :this.constructCurrentlyInUse) {
						if(con.getIfAt() == endOfIfBlock) {
							this.constructCurrentlyInUse.remove(con);
							break;
						}
					}
				}
				else {
					positionInProgram = findCorrespondingClosingBracket(openingBracketJustBeforeConstruct);
					for(Construct con :this.constructCurrentlyInUse) {
						if(con.getIfAt() == this.positionInProgram) {
							this.constructCurrentlyInUse.remove(con);
							break;
						}
					}
				}
				
				execute1Step();
			}
		}
	}
	private int findClosestOpeningBracketLeftfrom(int position) {
		for(int start = position ; start > 0 ; start--) {
			if(program.charAt(start) == '(') {
				return start;
			}
		}
		return 0;
	}
	private int findClosestOpeningBracketRightfrom(int position) {
		for(int start = position ; start < program.length() ; start++) {
			if(program.charAt(start) == '(') {
				return start;
			}
		}
		return program.length()-1;
	}
	/**
	 * Execute the given CommandWordsBasicCommand, with the needed arguments
	 * @param command the command to be executed
	 */
	private void executeBasicCommand(CommandWordsBasicCommand command) {
		// The 1 argument is always the robot
		Object[] arguments = new Object[2];
		arguments[0] = robot;
		if(command.getArgs().length == 1) {
			command.execute(arguments);
			return;
		}
		if(command.getArgs().length == 2) {
			// If 2 arguments are needed only a String is possible
			arguments[1] = (String) getNextEntry(true);
			command.execute(arguments);
			return;
		}
		
		
	}
	/**
	 * Evaluate the given CommandWordsCondition, with robot as argument
	 * @param condition the condition to be evaluated
	 * @return the result of the evaluation of the condition
	 */
	private boolean executeBasicCondition(CommandWordsCondition condition) {
		Class<?>[] args = condition.getArguments();
		Object[] arguments = new Object[args.length];
		if(args[0] == Robot.class) {
			arguments[0] = this.robot;
		}
		return condition.evaluate(arguments);
	}
	/**
	 * Evaluate the given CommandWordsCondition, with arguments in the String
	 * @param argument
	 * @param condition the condition to be evaluated
	 * @return the result of the evaluation of the condition
	 */
	private boolean executeBasicCondition(CommandWordsCondition condition,String argument) {
		Class<?>[] args = condition.getArguments();
		Object[] arguments = new Object[args.length];
		if(args[0] == Robot.class) {
			arguments[0] = this.robot;
		}
		if(args[1] == Integer.class) {
			arguments[1] = (Integer)Integer.parseInt(argument);
		}
		return condition.evaluate(arguments);
	}
	/**
	 * Execute a Condition while adjusting positionInProgram so that it is after the condition when this method returns
	 */
	private boolean executeCondition() {
		//The condition to evaluate must start with a CommandWordBooleanOperator or a CommandWordsCondition
		CommandWordsCondition commandcond = null;
		// the next word in the program
		String comman = getNextEntry(true);
		// If the command is a CommandWordsCondition, execute it
		for(CommandWordsCondition cond : CommandWordsCondition.values()) {
			if(cond.getText().equals(comman)) {
				commandcond = cond;
			}
		}
		if(commandcond != null) {
			if(commandcond.getArguments().length == 1) {
				return executeBasicCondition(commandcond);
			}
			if(commandcond.getArguments().length == 2){
				return executeBasicCondition(commandcond,getNextEntry(true));
			}
			
			
		}
		// If the command is a CommandWordBooleanOperator, execute it
		CommandWordBooleanOperator operator = null;
		for(CommandWordBooleanOperator oper : CommandWordBooleanOperator.values()) {
			if(oper.getText().equals(comman)) {
				operator = oper;
			}
		}
		if(operator != null) {
			int numberOfArguments = operator.getNumberOfArguments();
			
			if(numberOfArguments == 1) {
				boolean resultOfCondition1 = executeCondition();
				Boolean[] arguments = new Boolean[numberOfArguments];
				arguments[0] = resultOfCondition1;
				return operator.evaluate(arguments);
			}
//			boolean resultOfCondition2;
//			if(numberOfArguments == 2) {
//				Boolean[] arguments = new Boolean[numberOfArguments];
//				arguments[0] = resultOfCondition1;
//				resultOfCondition2 = executeCondition();
//				arguments[1] = resultOfCondition2;
//				return operator.evaluate(arguments);
//			}
			int positionOfClosingBracket = findCorrespondingClosingBracket( findClosestOpeningBracketLeftfrom(positionInProgram) );
			List<Boolean> listOfArguments = new ArrayList<Boolean>();
			while(!jumpOutOfLoop(positionInProgram, positionOfClosingBracket) ) {
				boolean b = executeCondition();
				listOfArguments.add(b);
			}
			Boolean[] args =( listOfArguments.toArray(new Boolean[listOfArguments.size()])) ;
			return operator.evaluate(args);
		}
		// if not a CommandWordBooleanOperator or a CommandWordsCondition as command, the program is not syntactically correct
		throw new WrongProgramFormatException();
	}
	private boolean jumpOutOfLoop(int start, int end) {
		if(start > end) {
			return true;
		}
		for(int tmp  = start; tmp <= end; tmp ++) {
			if(program.charAt(tmp) != ' ' && program.charAt(tmp) != ')') {
				return false;
			}
		}
		return true;
	}
	/**
	 * Set the positionInProgram to the next openingBracket
	 * @throws Exception when no next 
	 */
	private void putPositionInProgramToNextOpeningBracket() {
		while(program.charAt(positionInProgram) != '(') {
			positionInProgram ++;
		}
	}
	/**
	 * Get the next word in the program,staring at the given location
	 * @param position
	 * @return
	 */
	private String getNextWordAt(int position) {
		int prevLocation = positionInProgram;
		positionInProgram = position;
		String toReturn = getNextEntry(true);
		positionInProgram = prevLocation;
		return toReturn;
	}
	
	/**
	 * find the corresponding closingBracket,staring at the given location
	 * @param openBracketLocation the location of the openingBracket
	 * @return the location of the closingBarcket the closes the openingBracket
	 */
	private int findCorrespondingClosingBracket(int openBracketLocation) {
		if(program.charAt(openBracketLocation) != '(') {
			throw new IllegalArgumentException();
		}
		// the number of extra bracketOpened
		int numberOfExtraBracketsOpened = 0;
		for(int location = openBracketLocation +1; location < program.length(); location ++) {
			if(program.charAt(location) == '(') {
				numberOfExtraBracketsOpened ++;
			}
			else if(program.charAt(location) == ')') {
				if(numberOfExtraBracketsOpened == 0) {
					return location;
				}
				numberOfExtraBracketsOpened --;
			}
		}
		//the program is not syntactically correct
		throw new WrongProgramFormatException("No corresponding closing bracket");
	}
	/**
	 * find the corresponding closingBracket,staring at the given location
	 * @param closeBracketLocation the location of the closeBracket
	 * @return  the location of the closingBarcket the closes the closeBracket
	 */
	private int findCorrespondingOpeningBracket(int closeBracketLocation) {
		if(program.charAt(closeBracketLocation) != ')') {
			throw new IllegalArgumentException();
		}
		// the number of extra bracketClosed
		int numberOfExtraBracketsClosed = 0;
		for(int location = closeBracketLocation-1; location >= 0; location --) {
			if(program.charAt(location) == ')') {
				numberOfExtraBracketsClosed ++;
			}
			else if(program.charAt(location) == '(') {
				if(numberOfExtraBracketsClosed == 0) {
					return location;
				}
				numberOfExtraBracketsClosed --;
			}
		}
		//the program is not syntactically correct
		throw new WrongProgramFormatException("No corresponding opening bracket");
	}
	/**
	 * Get the next command starting from positionInProgram. 
	 * positionInProgram is adjusted to right after the word (so not after a loop or so)
	 */
	private String getNextEntry(boolean toPutPosition) {
		int start = positionInProgram;
		while(start < program.length() && 
				(program.charAt(start) == '(' || program.charAt(start) == ' ' 
				 || program.charAt(start) == ')' || program.charAt(start) =='	') ) {
			start ++;
		}
		// Now we are sure that start is indeed the starting character of the command we want?
		for(int end = start;end < program.length(); end ++) {
			if(program.charAt(end) == ' ' || program.charAt(end) == '(' || program.charAt(end) == ')') {
				if(toPutPosition) {
					positionInProgram = end;
				}
				// subString returns from start to end-1 so ' ' or ')' is not included
				return program.substring(start,end);
			}
		}
		//the program is not syntactically correct
		throw new WrongProgramFormatException("No next entry found");
	}
}


